/*It only start from parent generate the child.
 * It should build from the AST and symbol table
 * Should have a standard interface class for general compatible with JAVA
 * The main model java represent the package
 * All the model import the default interface
 * abstract class means table can combine class into one
 * static means must be stand as itself
 * THE SYNTAX NEED TO BE REVISITED AFTER THE EXPERIMENT.
 * For the interested it maybe need the field name suffix
 * Enum should be generated as types in table.
 * MethodToJAVATypeMap is good concept and should extend used
 * For example: add zero and non-zero. And less (((())))
 * Get the Result Set by different criteria
 */

package samples;

import java.io.File;
import java.io.FileReader;
import java.io.IOException;
import java.io.PrintWriter;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.LinkedList;
import java.util.List;
import org.antlr.stringtemplate.StringTemplate;
import org.antlr.stringtemplate.StringTemplateGroup;

public class GaeJavaGen {

	private static ArrayList<Class<?>> classes;
	private final static String stgPath = "src/main/resources/";
	private final static String outPutPath = "src/main/java/gaegen/";
	private final static String target = "CompositeModel$menu";
	private final static String pkgname = "samples";
	private static String suffix;
	private static String groupFile;

	public static void main(String[] args) throws IOException,
			SecurityException, NoSuchFieldException, ClassNotFoundException {

		// LOAD all classes
		ClassLoader classLoader = Thread.currentThread()
				.getContextClassLoader();
		File pkg = new File(classLoader.getResources(pkgname).nextElement()
				.getFile());
		classes = new ArrayList<Class<?>>();
		File[] files = pkg.listFiles();
		for (File file : files) {
			if (file.getName().endsWith(".class")) {
				classes.add(Class.forName(pkgname
						+ '.'
						+ file.getName().substring(0,
								file.getName().length() - 6)));
			}
		}
		LinkedList<member> members = new LinkedList<member>();
		Class<?> c = Class.forName(pkgname + "." + target);
		buildMembers(c, members, c);
		genSQL(c, members);
		genJAVA(c, members);
		// System.out.println(output.toString());
	}

	public static void genSQL(Class<?> c, LinkedList<member> members) throws SecurityException,
			NoSuchFieldException, IOException {
		suffix = ".sql";
		groupFile = stgPath + "Sql.stg";
		FileReader fr = new FileReader(groupFile);
		StringTemplateGroup templates = new StringTemplateGroup(fr);
		fr.close();
		templates.registerRenderer(Class.class, new TypeRenderer());
		PrintWriter outfile = new PrintWriter(new File(outPutPath
				+ c.getSimpleName() + suffix));
		for (member m : members) {
			StringTemplate classST = templates.getInstanceOf("output");
			classST.setAttribute("class", m.cls);
			classST.setAttribute("fields", m.fields);
			classST.setAttribute("arrayFields", m.arrayFields);
			classST.setAttribute("methodFields", m.methodFields);
			outfile.print(classST);
		}
		outfile.flush();
	}

	protected static void buildMembers(Class<?> currentClass,
			LinkedList<member> memberList, Class<?> parentClass)
			throws SecurityException, NoSuchFieldException {
		List<column> fields = new ArrayList<column>();
		List<column> arrayFields = new ArrayList<column>();
		List<column> methodFields = new ArrayList<column>();

		for (member m : memberList) {
			if (m.cls.simpleName.equals(currentClass.getSimpleName())) {
				return;
			}
		}
		memberList.add(new member(new table(currentClass.getSimpleName(),
				currentClass.isInterface(),
				currentClass.getInterfaces().length == 0), fields, arrayFields,
				methodFields, arrayToList(currentClass.getInterfaces())));
		List<Class<?>> parentList = getParent(currentClass);
		if (currentClass.isInterface()) {
			methodFields.addAll(arrayToList(currentClass.getMethods()));
			return;
		} else if (methodFields.isEmpty()) {
			methodFields.addAll(arrayToList(currentClass.getMethods()));
		}
		if (!currentClass.equals(parentClass)) {
			fields.add(new column(parentClass.getSimpleName() + "_ID", "int",
					parentClass, classes));
			fields.add(new column(parentClass.getSimpleName() + "_EntityType", "String",
					parentClass, classes));
		}

		for (Field f : currentClass.getFields()) {
			if (f.getType().isArray()) {
				arrayFields.add(new column(f.getName(), f.getType()
						.getSimpleName(), f.getType(), classes));
				List<Class<?>> arrayParentList = getParent(f.getType()
						.getComponentType());
				Class<?> parent = getParent(arrayParentList, parentList);
				if (parent == null) {
					buildMembers(f.getType().getComponentType(), memberList,
							f.getDeclaringClass());
				} else {
					for (Class<?> c : classes) {
						List<Field> parentFields = new ArrayList<Field>(
								Arrays.asList(currentClass.getFields()));
						boolean sw = true;
						if (currentClass != c && getParent(c).contains(parent)) {
							if (!c.isInterface()) {
								buildMembers(parent, memberList, parent);
								List<Field> currentFields = new ArrayList<Field>(
										Arrays.asList(c.getFields()));
								currentFields.removeAll(parentFields);
								if (Modifier.isStatic(c.getModifiers())) {
									buildMembers(c, memberList, currentClass);
								} else {
									for (Field columnInParent : currentFields) {
										fields.add(new column(columnInParent
												.getName(), columnInParent
												.getType().getSimpleName(),
												columnInParent.getType(),
												classes));
									}
								}
								if (sw) {
									fields.add(new column(currentClass
											.getSimpleName() + "_ID", "int",
											currentClass, classes));
									sw = false;
								}
							} else {
								buildMembers(c, memberList, parent);
							}
						}
					}
				}
			} else {
				for (Class<?> c : classes) {
					if (currentClass != c
							&& !(getParent(getParent(c), parentList) == null)
							&& !In(currentClass, c)) {
						buildMembers(c, memberList, c);
					}
				}
				List<Class<?>> currentList = getParent(f.getType());
				int i = currentList.size();
				currentList.removeAll(parentList);
				if (i == currentList.size()) {
					fields.add(new column(f.getName(), f.getType()
							.getSimpleName(), f.getType(), classes));
				}
			}
		}
	}

	private static boolean In(Class<?> currentclass, Class<?> c) {
		for (Field f : currentclass.getFields()) {
			if (f.getType().isArray()) {
				if (!(getParent(getParent(c), getParent(f.getType()
						.getComponentType())) == null)) {
					return true;
				}
			} else {
				if (!(getParent(getParent(c), getParent(f.getType())) == null)) {
					return true;
				}
			}
		}
		return false;
	}

	public static void genJAVA(Class<?> c, LinkedList<member> members) throws SecurityException,
			NoSuchFieldException, IOException {
		suffix = ".java";
		groupFile = stgPath + "JavaModel.stg";
		FileReader fr = new FileReader(groupFile);
		StringTemplateGroup templates = new StringTemplateGroup(fr);
		fr.close();
		templates.registerRenderer(Class.class, new TypeRenderer());
		for (member m : members) {
			PrintWriter outfile = new PrintWriter(new File(outPutPath
					+ m.cls.simpleName + suffix));
			StringTemplate classST = templates.getInstanceOf("output");
			classST.setAttribute("class", m.cls);
			classST.setAttribute("fields", m.fields);
			classST.setAttribute("arrayFields", m.arrayFields);
			classST.setAttribute("methodFields", m.methodFields);
			classST.setAttribute("interfaceImps", m.interfaceImps);
			outfile.print(classST);
			outfile.flush();
			outfile.close();
		}
		groupFile = stgPath + "Java.stg";
		fr = new FileReader(groupFile);
		templates = new StringTemplateGroup(fr);
		fr.close();
		for (member m : members) {
			if (!m.cls.isInterface) {
				PrintWriter outfile = new PrintWriter(new File(outPutPath
						+ m.cls.simpleName + "IO" + suffix));
				StringTemplate classST = templates.getInstanceOf("output");
				classST.setAttribute("class", m.cls);
				classST.setAttribute("fields", m.fields);
				classST.setAttribute("arrayFields", m.arrayFields);
				classST.setAttribute("methodFields", m.methodFields);
				outfile.print(classST);
				outfile.flush();
				outfile.close();
			}
		}
	}

	private static Class<?> getParent(List<Class<?>> currentList,
			List<Class<?>> parentList) {
		for (Class<?> s1 : parentList) {
			for (Class<?> s2 : currentList) {
				if (s1.equals(s2)) {
					return s2;
				}
			}
		}
		return null;
	}

	public static List<Class<?>> getParent(Class<?> c) {
		List<Class<?>> r = new LinkedList<Class<?>>();
		r.add(c);
		Class<?>[] nxtParent = c.getInterfaces();
		while (nxtParent.length > 0) {
			r.add(nxtParent[0]);
			nxtParent = nxtParent[0].getInterfaces();
		}
		return r;
	}

	private static List<column> arrayToList(Class<?>[] list) {
		List<column> interfaceImps = new ArrayList<column>();
		for (Class<?> c : list) {
			interfaceImps.add(new column(c.getSimpleName(), "intferace", c,
					classes));
		}
		return interfaceImps;
	}

	private static List<column> arrayToList(Method[] list) {
		List<column> interfaceImps = new ArrayList<column>();
		for (Method c : list) {
			if (c.getDeclaringClass().getPackage().getName().equals(pkgname))
				interfaceImps.add(new column(c.getName(), c.getReturnType()
						.getSimpleName(), c.getReturnType(), classes));

		}
		return interfaceImps;
	}

}

class member {
	table cls;
	List<column> fields;
	List<column> arrayFields;
	List<column> methodFields;
	List<column> interfaceImps;

	member(table c, List<column> f, List<column> af, List<column> n,
			List<column> i) {
		this.cls = c;
		this.fields = f;
		this.arrayFields = af;
		this.methodFields = n;
		this.interfaceImps = i;
	}
	
	@Override
    public String toString() {
		return cls.simpleName;
	}
}

class table {
	public String simpleName;
	public boolean isInterface;
	public boolean isImp;

	table(String name, boolean b, boolean c) {
		this.simpleName = name;
		this.isInterface = b;
		this.isImp = c;
	}
}

class column {
	public String name;
	public String type;
	public String componentType;
	public List<column> childClasses;

	column(String name, String type, Class<?> c, List<Class<?>> classes) {
		this.name = name;
		this.type = type;
		this.componentType = getComType(c);
		this.childClasses = getChildren(c, classes);
	}

	private List<column> getChildren(Class<?> parent, List<Class<?>> classes) {
		List<column> children = new ArrayList<column>();
		if (classes != null) {
			for (Class<?> c : classes) {
				if (!c.isInterface()
						&& isChild(c.getInterfaces(),
								parent.isArray() ? parent.getComponentType()
										: parent)) {
					children.add(new column(c.getSimpleName(), null, null, null));
				}
			}
		}
		if (children.isEmpty() && parent != null) {
			children.add(new column((parent.isArray() ? parent
					.getComponentType() : parent).getSimpleName(), null, null,
					null));
		}
		return children;
	}

	private boolean isChild(Class<?>[] interfaces, Class<?> parent) {
		for (Class<?> c : interfaces) {
			if (GaeJavaGen.getParent(c).contains(parent)) {
				return true;
			}
		}
		return false;
	}

	private static String getComType(Class<?> c) {
		if (c != null && c.isArray()) {
			return c.getComponentType().getSimpleName();
		}
		return null;
	}
}
